//------------------------------------------------------------------------------
// <copyright file="SetIterators.cs" company="Microsoft">
//     
//      Copyright (c) 2006 Microsoft Corporation.  All rights reserved.
//     
//      The use and distribution terms for this software are contained in the file
//      named license.txt, which can be found in the root of this distribution.
//      By using this software in any fashion, you are agreeing to be bound by the
//      terms of this license.
//     
//      You must not remove this notice, or any other, from this software.
//     
// </copyright>                                                                
//------------------------------------------------------------------------------
using System;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;

namespace System.Xml.Xsl.Runtime {

    /// <summary>
    /// Set iterators (Union, Intersection, Difference) that use containment to control two nested iterators return
    /// one of the following values from MoveNext().
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public enum SetIteratorResult {
        NoMoreNodes,                // Iteration is complete; there are no more nodes
        InitRightIterator,          // Initialize right nested iterator
        NeedLeftNode,               // The next node needs to be fetched from the left nested iterator
        NeedRightNode,              // The next node needs to be fetched from the right nested iterator
        HaveCurrentNode,            // This iterator's Current property is set to the next node in the iteration
    };


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// Using a merge sort, this operator returns the union of these sets in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct UnionIterator {
        private XmlQueryRuntime runtime;
        private XPathNavigator navCurr, navOther;
        private IteratorState state;

        private enum IteratorState {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            LeftIsCurrent,
            RightIsCurrent,
        };

        /// <summary>
        /// Create SetIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime) {
            this.runtime = runtime;
            this.state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator) {
            switch (this.state) {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    this.navOther = nestedNavigator;
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    this.navCurr = nestedNavigator;
                    this.state = IteratorState.LeftIsCurrent;
                    break;

                case IteratorState.NeedRight:
                    this.navCurr = nestedNavigator;
                    this.state = IteratorState.RightIsCurrent;
                    break;

                case IteratorState.LeftIsCurrent:
                    // Just returned left node as current, so get new left
                    this.state = IteratorState.NeedLeft;
                    return SetIteratorResult.NeedLeftNode;

                case IteratorState.RightIsCurrent:
                    // Just returned right node as current, so get new right
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
            }

            // Merge left and right nodes
            if (this.navCurr == null) {
                // If both navCurr and navOther are null, then iteration is complete
                if (this.navOther == null)
                    return SetIteratorResult.NoMoreNodes;

                Swap();
            }
            else if (this.navOther != null) {
                int order = this.runtime.ComparePosition(this.navOther, this.navCurr);

                // If navCurr is positioned to same node as navOther,
                if (order == 0) {
                    // Skip navCurr, since it is a duplicate
                    if (this.state == IteratorState.LeftIsCurrent) {
                        this.state = IteratorState.NeedLeft;
                        return SetIteratorResult.NeedLeftNode;
                    }

                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
                }

                // If navOther is before navCurr in document order, then swap navCurr with navOther
                if (order < 0)
                    Swap();
            }

            // Return navCurr
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current {
            get { return this.navCurr; }
        }

        /// <summary>
        /// Swap navCurr with navOther and invert state to reflect the change.
        /// </summary>
        private void Swap() {
            XPathNavigator navTemp = this.navCurr;
            this.navCurr = this.navOther;
            this.navOther = navTemp;

            if (this.state == IteratorState.LeftIsCurrent)
                this.state = IteratorState.RightIsCurrent;
            else
                this.state = IteratorState.LeftIsCurrent;
        }
    }


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// This iterator returns the intersection of these sets in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct IntersectIterator {
        private XmlQueryRuntime runtime;
        private XPathNavigator navLeft, navRight;
        private IteratorState state;

        private enum IteratorState {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            NeedLeftAndRight,
            HaveCurrent,
        };

        /// <summary>
        /// Create IntersectIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime) {
            this.runtime = runtime;
            this.state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator) {
            int order;

            switch (this.state) {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    this.navLeft = nestedNavigator;
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    this.navLeft = nestedNavigator;
                    break;

                case IteratorState.NeedRight:
                    this.navRight = nestedNavigator;
                    break;

                case IteratorState.NeedLeftAndRight:
                    // After fetching left node, still need right node
                    this.navLeft = nestedNavigator;
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;

                case IteratorState.HaveCurrent:
                    // Just returned left node as current, so fetch new left and right nodes
                    Debug.Assert(nestedNavigator == null, "null is passed to MoveNext after IteratorState.HaveCurrent has been returned.");
                    this.state = IteratorState.NeedLeftAndRight;
                    return SetIteratorResult.NeedLeftNode;
            }

            if (this.navLeft == null || this.navRight == null) {
                // No more nodes from either left or right iterator (or both), so iteration is complete
                return SetIteratorResult.NoMoreNodes;
            }

            // Intersect left and right sets
            order = this.runtime.ComparePosition(this.navLeft, this.navRight);

            if (order < 0) {
                // If navLeft is positioned to a node that is before navRight, skip left node
                this.state = IteratorState.NeedLeft;
                return SetIteratorResult.NeedLeftNode;
            }
            else if (order > 0) {
                // If navLeft is positioned to a node that is after navRight, so skip right node
                this.state = IteratorState.NeedRight;
                return SetIteratorResult.NeedRightNode;
            }

            // Otherwise, navLeft is positioned to the same node as navRight, so found one item in the intersection
            this.state = IteratorState.HaveCurrent;
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current {
            get { return this.navLeft; }
        }
    }


    /// <summary>
    /// This iterator manages two sets of nodes that are already in document order with no duplicates.
    /// This iterator returns the difference of these sets (Left - Right) in document order with no duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct DifferenceIterator {
        private XmlQueryRuntime runtime;
        private XPathNavigator navLeft, navRight;
        private IteratorState state;

        private enum IteratorState {
            InitLeft = 0,
            NeedLeft,
            NeedRight,
            NeedLeftAndRight,
            HaveCurrent,
        };

        /// <summary>
        /// Create DifferenceIterator.
        /// </summary>
        public void Create(XmlQueryRuntime runtime) {
            this.runtime = runtime;
            this.state = IteratorState.InitLeft;
        }

        /// <summary>
        /// Position this iterator to the next node in the union.
        /// </summary>
        public SetIteratorResult MoveNext(XPathNavigator nestedNavigator) {
            switch (this.state) {
                case IteratorState.InitLeft:
                    // Fetched node from left iterator, now get initial node from right iterator
                    this.navLeft = nestedNavigator;
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.InitRightIterator;

                case IteratorState.NeedLeft:
                    this.navLeft = nestedNavigator;
                    break;

                case IteratorState.NeedRight:
                    this.navRight = nestedNavigator;
                    break;

                case IteratorState.NeedLeftAndRight:
                    // After fetching left node, still need right node
                    this.navLeft = nestedNavigator;
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;

                case IteratorState.HaveCurrent:
                    // Just returned left node as current, so fetch new left node
                    Debug.Assert(nestedNavigator == null, "null is passed to MoveNext after IteratorState.HaveCurrent has been returned.");
                    this.state = IteratorState.NeedLeft;
                    return SetIteratorResult.NeedLeftNode;
            }

            if (this.navLeft == null) {
                // If navLeft is null, then difference operation is complete
                return SetIteratorResult.NoMoreNodes;
            }
            else if (this.navRight != null) {
                int order = this.runtime.ComparePosition(this.navLeft, this.navRight);

                // If navLeft is positioned to same node as navRight,
                if (order == 0) {
                    // Skip navLeft and navRight
                    this.state = IteratorState.NeedLeftAndRight;
                    return SetIteratorResult.NeedLeftNode;
                }

                // If navLeft is after navRight in document order, then skip navRight
                if (order > 0) {
                    this.state = IteratorState.NeedRight;
                    return SetIteratorResult.NeedRightNode;
                }
            }

            // Return navLeft
            this.state = IteratorState.HaveCurrent;
            return SetIteratorResult.HaveCurrentNode;
        }

        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned -1.
        /// </summary>
        public XPathNavigator Current {
            get { return this.navLeft; }
        }
    }
}
